import music21.corpus
import templates     # Basic structures (e.g. sim/seq) entering in viewpoint definitions
import features      # The values returned by applying viewpoints
import types_        # Viewpoint types
import results       # Objects to store the result of applying a viewpoint
import mining        # Data mining functionaliites
import display       # All display/visualisation functionalities
import utils         # Helper functions
import itertools     # For the count iterator
import copy

# TODOs and FIXMES:
#
# + Must anticipate the need for voice variables: how to incorporate them? (and other variables?)
# + Clarified how results are stored and how to deal with a single notes participating in more than one result
#
#

DEMODATA = music21.corpus.parseWork('bach/bwv7.7')
DEMOSIZE = 6



class VP():
	"""Note --> Null: the generic viewpoint.  Forgets everything about a note.

	Do not print in the list of viewpoint.  This is a 'utility' viewpoint

	"""
	displayInfo = False

	def __init__(self):
		try:
			self.__doc__ = str(self.template) + " --> " + str(self.type_) + "\n" +  self.__doc__
		except AttributeError:
			pass

	def computeResultDict(self,stream):

		# From the template, get an iterator over 'sturctures of notes'
		# Every structure is itself an iterator over its elements, which can be notes or more structures
		self.template.setStream(stream)
		results_ = results.Result()
		for structure_iterator in self.template:
			# FIXED: vpFunction now returns a single tuple.  Maybe we want to expand on this later
			(note,feature) = self.vpFunction(structure_iterator)
			results_[note.id]=(note,feature)

		return results_

	def apply(self,stream,*args,**keywords):
		try: slice_ = keywords['slice']
		except KeyError: slice_ = False

		# TODO: how/when to select the material for the demo
		# The demo material must contain the template at least once
		# And must ressemble the structure of the stream
		sliced_stream = copy.deepcopy(stream)

		if slice_:
			self.template.sliceStream(sliced_stream,slice_)


		result_dict = self.computeResultDict(sliced_stream)
		
		# Add values to the lyrics field for all assignation
		for note_id,(note,feature) in result_dict.items():
			if note.lyric:
				note.lyric = note.lyric + "\n" + str(feature.name)+" : "+str(feature.value)
			else:
				note.lyric = str(feature.name)+" : "+str(feature.value)

		return sliced_stream

	def demo(self, stream=DEMODATA, *args,**keywords):
		try: keywords['slice']
		except KeyError: keywords['slice'] = DEMOSIZE

		applied_stream = self.apply(stream,*args,**keywords)

		applied_stream.show()

	def report(self):
		pass

class PitchVP(VP):
	"""Note --> Pitch: returns the pitch of a note.
    
	"""
	displayInfo = True

	def __repr__(self):
		return "pitch"


	def __init__(self):
		self.template = templates.NoteTemplate()
		self.type_ = types_.PitchType()
		VP.__init__(self)


	def vpFunction(self,note):
		return (note,features.PitchFeature(note.pitch))
		

class DurationVP(VP):
	"""Note --> Duration: returns the duration of a note.

	"""
	displayInfo = True


	def __repr__(self):
		return "duration"

	def __init__(self):
		self.template = templates.NoteTemplate()
		self.type_ = types_.DurationType()
		VP.__init__(self)


	# FIXME: it is a bit weird that the vpFunction has to 'decide' to which note attribute the value
	# This should be decided elsewhere, and the vpFunction should simply return a feature
	def vpFunction(self,note):
		return (note,features.DurationFeature(note.quarterLength))


class UserVPException(Exception):
	pass

class UserVP(VP):
	"""A class to facilitate the creation of user-defined VP.
	
	"""

	displayInfo = True

	def __init__(self,shortname,longname,template_expression,function,assign_index=0):
		self.shortname = shortname
		self.longname = longname
		(self.type, self.template) = self.analyze_template_expression(template_expression)
		self.template_expression = template_expression
		self.function = function
		self.assign_index = assign_index
		VP.__init__(self)


		self.__doc__ = str(self.type) + " --> " + self.shortname


	def __repr__(self):
		return self.longname

	def vpFunction(self,structure):
		# Before applying the function, apply other VPs to the structure
		# This returns an iterator of values.
		# The VP function is actually applied on this, not the structure of notes
		
		class ValueIterator_:
			def __init__(self,template_expression,structure):
				self.template_expression = template_expression
				self.structure = structure

			def __iter__(self):
				self.structure_iter = self.structure.__iter__()
				self.expression_iter = self.template_expression.__iter__()
				return self

			def next(self):
				next_structure = self.structure_iter.next()
				next_vp = self.expression_iter.next()

				# FIXME: is this what we want. Perhaps we need a clearer structure for the results
				# E.g. an iterator on results, a result being an iterator on (note,value) pairs

				# FIXME: doesn't that need to recurse into the structure?
				(next_note,next_val) = next_vp.vpFunction(next_structure)
				return next_val

			def __getitem__(self,index):
				i = 0
				for v in self:
					if i == index:
						return v
					else:
						i+=1

		feature = self.function(ValueIterator_(self.template_expression,structure))
		feature.name = self.shortname
		return (structure[self.assign_index],feature)

	def analyze_type(self,type_expression):
		if isinstance(type_expression,list):
			return types_.SequenceType(map(self.analyze_type,type_expression))
		if isinstance(type_expression,types_.Type):
			return type_expression
		if isinstance(type_expression,VP):
			return str(type_expression)

		return None

	def analyze_template(self,template_expression):
		if isinstance(template_expression,list):
			return templates.SequenceTemplate(len(template_expression))

		return None

	def analyze_template_expression(self,template_expression):
		type_ = self.analyze_type(template_expression)
		template = self.analyze_template(template_expression)


		if template or type_:
			return (type_,template)
		else:
			raise UserVPException("Could not analyze template expression: " + str(template_expression))



# FIXME: this should also list user-defined viewpoints
def listViewpoints():
	print "predefined viewpoints:"
	utils.list_sub_classes('viewpoints','VP')
	print ""
	import constructors; constructors.listConstructors()
	print "\nuser-defined viewpoints:"
	utils.list_instances('__main__','VP')
	print ""
	print ""


def listTemplates():
	pass

